/*
 * Copyright 2011-2018 ijym-lee
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package me.ijleex.openapi.oauth.config;

import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.util.Assert;

/**
 * 配置 授权服务器
 *
 * @author liym
 * @since 2018-11-30 15:18 新建
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    /**
     * 数据源
     */
    private final DataSource dataSource;
    /**
     * 认证管理器，用于支持 grant_type="password" 模式
     */
    private final AuthenticationManager authenticationManager;

    /**
     * 构造器注入
     *
     * @param dataSource 注入的对象：数据源
     * @param authenticationManager 注入的对象：认证管理器
     */
    @Autowired
    public AuthorizationServerConfiguration(DataSource dataSource, AuthenticationManager authenticationManager) {
        Assert.notNull(dataSource, "DataSource required");
        Assert.notNull(authenticationManager, "AuthenticationManager required");
        this.dataSource = dataSource;
        this.authenticationManager = authenticationManager;
    }

    /**
     * 配置令牌端点（Token Endpoint）的安全约束
     *
     * @see AuthorizationServerSecurityConfigurer#sslOnly()
     * @see org.springframework.security.access.expression.SecurityExpressionOperations#permitAll()
     * @see org.springframework.security.access.expression.SecurityExpressionOperations#isAuthenticated()
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer authServer) throws Exception {
        authServer.allowFormAuthenticationForClients();
        authServer.tokenKeyAccess("permitAll()");
        // 允许接口 /oauth/check_token 被调用
        authServer.checkTokenAccess("permitAll()");
    }

    /**
     * 配置客户端详情服务，客户端详情信息在这里进行初始化
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 基于 JDBC 存储令牌
        clients.jdbc(this.dataSource);
    }

    /**
     * 配置授权（Authorization）、令牌（Token）的访问端点和令牌服务（TokenServices）
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore());
        endpoints.authenticationManager(this.authenticationManager);

        // 配置 TokenServices 参数，解决获取 Token 并发问题
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));
        endpoints.tokenServices(tokenServices);

        endpoints.authorizationCodeServices(authorizationCodeServices());
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(this.dataSource);
    }

    /**
     * @since 2018-12-27 14:33
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(this.dataSource);
    }

}
